﻿using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using Simple.Services.System.FileInfo.Models;
using System.Linq;
using System.Security.Cryptography;
using System.Text;

namespace Simple.Services.System.FileInfo
{
    public class FileInfoService: BusinessBaseService
    {
        private readonly SimpleDbContext _context;
        private readonly IConfiguration _configuration;
        private readonly ICurrentUserService _currentUserService;
        private readonly CacheService _cacheService;
        private readonly ILogger<FileInfoService> _logger;

        public FileInfoService(SimpleDbContext context, 
            IConfiguration configuration,
            ISimpleService simpleService,
            ICurrentUserService currentUserService,
            CacheService cacheService,
            ILogger<FileInfoService> logger) : base(simpleService)
        {
            _context = context;
            _configuration = configuration;
            _currentUserService = currentUserService;
            _cacheService = cacheService;
            _logger = logger;
        }

        public async Task<PageResultModel<FileInfoModel>> GetPageAsync(FilePageInputModel input)
        {
            var files = _context.Set<SysFileInfo>().AsQueryable();
            var result = new PageResultModel<FileInfoModel>();
            var query = from file in files
                        join user in _context.Set<SysUser>()
                           on file.CreatedUserId equals user.Id
                        where  file.IsDeleted == false && file.IsSystemFile == false
                        orderby file.IsFolder descending, file.CreatedTime descending
                        select new { file, user };

            // 根据条件查询
            if (input.FolderId.HasValue)
            {
                query = query.Where(a => a.file.FolderId == input.FolderId);
            }
            else
            {
                query = query.Where(a => a.file.FolderId == null);
            }

            if (!string.IsNullOrEmpty(input.Name))
            {
                query = query.Where(a => EF.Functions.Like(a.file.FileName, $"%{input.Name}%"));
            }

            // 获取总数量
            result.TotalRows = await query.CountAsync();

            // 分页查询
            query = query.Page(input.PageNo, input.PageSize);
            result.Rows = await query.Select(s => new FileInfoModel
            {
                FileName = s.file.FileName,
                FolderId = s.file.FolderId,
                FilePath = s.file.FilePath,
                FileSize = s.file.FileSize,
                Filesuffix = s.file.Filesuffix,
                CreatedTime =s.file.CreatedTime,
                CreatedUserId = s.file.CreatedUserId,
                CreatedUserName = s.user.UserName,
                Id = s.file.Id,
                IsFolder = s.file.IsFolder,
                Remark  = s.file.Remark
            }).ToListAsync();

            result.SetPage(input);
            result.CountTotalPage();

            return result;
        }

        public async Task<Guid> UploadAsync(FileInfoInputModel model)
        {
            if (model == null || model.File == null)
            {
                throw AppResultException.Status400BadRequest("参数错误，未选择文件");
            }

            var fileName = model.File.FileName;
            Guid? folderId = null;
            SysFileInfo? folderInfo = null;
            if (!string.IsNullOrWhiteSpace(model.Folder))
            {
                folderId = Guid.Parse(model.Folder);
                folderInfo = await _context.Set<SysFileInfo>().FirstOrDefaultAsync(s => s.Id == folderId);
            }
            if (!model.BusinessType.HasValue && await _context.Set<SysFileInfo>().AnyAsync(m => m.FileName == fileName && m.FolderId == folderId && m.IsDeleted == false))
            {
                throw AppResultException.Status409Conflict("当前文件夹下已存在同名文件，请重命令文件或者上传至其他文件夹");
            }
            var defaultPath = string.Empty;
            if (model.BusinessType.HasValue)
            {
                defaultPath = "system";
            }

            var file = new SysFileInfo
            {
                FileName = fileName,
                FilePath = folderInfo != null ? folderInfo.FilePath : defaultPath,
                Filesuffix = Path.GetExtension(fileName),
                FileSize = GetFileSize(model.File.Length),
                HashFileName = $"{DateTime.Now.ToString("yyyyMMddHHmmss")}_{fileName}",
                IsFolder = false,
                IsSystemFile = false,
                FolderId = folderId,
            };
            if (model.BusinessType.HasValue)
            {
                file.IsSystemFile = true;
                file.BusinessType = model.BusinessType.ToString() ?? string.Empty;
                file.BusinessId = model.BusinessId ?? string.Empty;          
            }

            var entity = await _context.AddAsync(file);
            await StorageFile(model.File, file);
            await _context.SaveChangesAsync();
            return entity.Entity.Id;
        }

        public async Task<List<Guid>> UploadFilesAsync(FileInfoInputModel model)
        {
            if (model == null || (model.Files == null || !model.Files.Any()))
            {
                throw AppResultException.Status400BadRequest("参数错误，未选择文件");
            }

            return await BusinessUploadAsync(model.Files, model.BusinessType ?? FileSource.Article, string.Empty);
        }

        private string GetFileSize(long size)
        {
            var num = 1024.00;
            if (size < num)
                return size + " B";
            if (size < Math.Pow(num, 2))
                return (size / num).ToString("f2") + " KB";
            if (size < Math.Pow(num, 3))
                return (size / Math.Pow(num, 2)).ToString("f2") + " MB";
            if (size < Math.Pow(num, 4))
                return (size / Math.Pow(num, 3)).ToString("f2") + " GB";
            return (size / Math.Pow(num, 4)).ToString("f2") + " TB";
        }

        private async Task StorageFile(IFormFile file, SysFileInfo fileInfo)
        {
            var localStorage = _configuration.GetValue<string>("LocalStorage");
            string fullPath = Path.Combine(localStorage, fileInfo.FilePath);
            if (!Directory.Exists(fullPath))
            {
                Directory.CreateDirectory(fullPath);
            }
            var filePath = Path.Combine(fullPath, fileInfo.HashFileName);
            if (File.Exists(filePath))
            {
                File.Delete(Path.GetFullPath(filePath));
            }

            using var stream = new FileStream(filePath, FileMode.Create);
            await file.CopyToAsync(stream);
            stream.Close();
        }

        public async Task DeleteAsync(IdInputModel model)
        {
            var file = await _context.Set<SysFileInfo>().FirstOrDefaultAsync(r => r.Id == model.Id);
            if (file != null)
            {
                if (file.IsFolder)
                {
                    var childrenFile = await _context.Set<SysFileInfo>().FirstOrDefaultAsync(r => r.FolderId == model.Id && r.IsDeleted == false);
                    if (childrenFile != null)
                    {
                        throw AppResultException.Status409Conflict("当前文件夹存在子文件夹或存在文件，不允许删除");
                    }
                }
                file.IsDeleted = true;
                _context.Update(file);
                await _context.SaveChangesAsync();
            }
        }

        public async Task<FileDownInputModel> DownFile(Guid fileId)
        {
            var fileInfo = await _context.Set<SysFileInfo>().FirstOrDefaultAsync(r => r.Id == fileId);
            if (fileInfo == null)
            {
                throw AppResultException.Status404NotFound("未找到源文件");
            }
            var localStorage = _configuration.GetValue<string>("LocalStorage");
            var filePath = Path.Combine(localStorage, fileInfo.FilePath, fileInfo.HashFileName);
            if (!File.Exists(filePath))
            {
                throw AppResultException.Status404NotFound("未找到源文件");
            }
            var fileDown = new FileDownInputModel();
            fileDown.FileName = fileInfo.FileName;
            fileDown.FileData = await File.ReadAllBytesAsync(filePath);
            return fileDown;
        }


        public async Task<string> GetUserPortrait(Guid userId)
        {
            var fileInfo = await _context.Set<SysFileInfo>().OrderByDescending(s=>s.CreatedTime).FirstOrDefaultAsync(s=>s.BusinessType == FileSource.UserPortrait.ToString() && s.BusinessId == userId.ToString());
            if (fileInfo != null)
            {
                //var localStorage = _configuration.GetValue<string>("LocalStorage");
                //var fileFullPath = Path.Combine(localStorage, fileInfo.FilePath, fileInfo.HashFileName);
                //var bytes = await File.ReadAllBytesAsync(fileFullPath);
                //var base64Str = Convert.ToBase64String(bytes);
                //return base64Str;
                return fileInfo.Id.ToString();
            }
            return string.Empty;
        }

        public async Task<string> GetBase64Str(SysFileInfo? fileInfo)
        {
            try
            { 
                if (fileInfo != null)
                {
                    var localStorage = _configuration.GetValue<string>("LocalStorage");
                    var fileFullPath = Path.Combine(localStorage, fileInfo.FilePath, fileInfo.HashFileName);
                    var bytes = await File.ReadAllBytesAsync(fileFullPath);
                    var base64Str = Convert.ToBase64String(bytes);
                    return base64Str;
                }
            }
            catch (Exception)
            {
                return string.Empty;
            }

            return string.Empty;
        }

        public async Task<string> GetBase64Str(Guid fileId)
        {
            try
            {
                var fileInfo = await _context.Set<SysFileInfo>().FirstOrDefaultAsync(s => s.Id == fileId);
                if (fileInfo != null)
                {
                    var localStorage = _configuration.GetValue<string>("LocalStorage");
                    var fileFullPath = Path.Combine(localStorage, fileInfo.FilePath, fileInfo.HashFileName);
                    var bytes = await File.ReadAllBytesAsync(fileFullPath);
                    var base64Str = Convert.ToBase64String(bytes);
                    return base64Str;
                }
            }
            catch (Exception)
            {
                return string.Empty;
            }

            return string.Empty;
        }

        public async Task AddFolder(FolderInputModel model)
        {
            Guid? folderId = null;
            SysFileInfo? folderInfo = null;
            if (!string.IsNullOrWhiteSpace(model.FolderId))
            {
                folderId = Guid.Parse(model.FolderId);
                folderInfo = await _context.Set<SysFileInfo>().FirstOrDefaultAsync(s => s.Id == folderId);
            }
            if (await _context.Set<SysFileInfo>().AnyAsync(m => m.FileName == model.FolderName && m.FolderId == folderId && m.IsDeleted == false))
            {
                throw AppResultException.Status409Conflict("当前目录下已存在同名文件夹");
            }
            var fileInfo = new SysFileInfo
            {
                FileName = model.FolderName,
                HashFileName = model.FolderName,
                FilePath = folderInfo == null ? model.FolderName : Path.Combine(folderInfo.FilePath, model.FolderName),
                IsFolder = true,
                FolderId = folderId,
            };
            var entity = await _context.AddAsync(fileInfo);
            await _context.SaveChangesAsync();
        }

        /// <summary>
        /// 业务方调用文件上传
        /// </summary>
        /// <param name="files">保存的文件列表</param>
        /// <param name="source">文件来源</param>
        /// <param name="businessId">业务Id</param>
        /// <param name="isSave">是否保存,true会在方法内保存，及时生效，false会和业务方一起保存</param>
        /// <returns></returns>
        public async Task<List<Guid>> BusinessUploadAsync(List<IFormFile> files, FileSource source, string businessId, bool isSave = true)
        {
            if (files == null || !files.Any())
            {
                throw AppResultException.Status400BadRequest("请上传文件");
            }
            var fileIds = new List<Guid>();
            foreach (var file in files)
            {
                var fileName = file.FileName;
                var fileInfo = new SysFileInfo
                {
                    FileName = file.FileName,
                    FilePath = "system",
                    Filesuffix = Path.GetExtension(file.FileName),
                    FileSize = GetFileSize(file.Length),
                    HashFileName = $"{DateTime.Now.ToString("yyyyMMddHHmmss")}_{fileName}",
                    IsFolder = false,
                    FolderId = null,
                    IsSystemFile = true,
                    BusinessType = source.ToString(),
                    BusinessId = businessId,
                };
                await StorageFile(file, fileInfo);
                var entity = await _context.AddAsync(fileInfo);
                fileIds.Add(entity.Entity.Id);
            }
            if(isSave)
                await _context.SaveChangesAsync();
            return fileIds;
        }

        #region 切片上传
        /// <summary>
        /// 获取文件续传信息
        /// </summary>
        /// <param name="folderId"></param>
        /// <param name="path"></param>
        /// <param name="fileName"></param>
        /// <param name="fileSize"></param>
        /// <param name="fileType"></param>
        /// <param name="blobCount"></param>
        /// <param name="blobSize"></param>
        /// <returns></returns>
        public async Task<FileResumableInputModel> GetFileResumableInfo(string folderId, string path, string fileName, long? fileSize, string fileType, long? blobCount, int? blobSize)
        {
            if (string.IsNullOrWhiteSpace(fileName) || string.IsNullOrWhiteSpace(path))
            {
                throw AppResultException.Status400BadRequest("切片参数异常");
            }
            string cleanKey = $"FolderId={folderId}&StoreName={path?.ToLower()}&FileName={fileName.ToLower()}&FileType={fileType?.ToLower()}&FileSize={fileSize}&BlobSize={blobSize}&BlobCount={blobCount}&user={_currentUserService?.UserId}";
            byte[] data;
            using (var md5 = MD5.Create())
            {
                data = md5.ComputeHash(Encoding.UTF8.GetBytes(cleanKey));
            }
            var sb = new StringBuilder();
            for (int i = 0; i < data.Length; i++)
            {
                sb.Append(data[i].ToString("X2"));
            }
            string md5key = sb.ToString();
            var cacheKey = _cacheService.GetKey(CacheKeyConst.FileSlice, md5key);
            var info = await _cacheService.GetCacheItemAsync<FileResumableInputModel>(cacheKey);
            if (info == null)
            {
                var now = DateTime.Now;
                info = new FileResumableInputModel
                {
                    Key = md5key,
                    StoreName = Path.Combine(path, $"{now.ToString("yyyyMMddHHmmss")}_{fileName}"),
                    FileName = fileName,
                    FileType = fileType,
                    FileSize = fileSize,
                    CreateDate = now,
                    BlobCount = blobCount,
                    Index = 0,
                    BlobSize = blobSize,
                    FolderId = folderId,
                    Path = path,
                };
                await _cacheService.SetCacheItemAsync(cacheKey, info, new Microsoft.Extensions.Caching.Distributed.DistributedCacheEntryOptions
                {
                    AbsoluteExpirationRelativeToNow = TimeSpan.FromDays(1)
                });
            }
            return info;
        }

        /// <summary>
        /// 文件上传
        /// 注：支持切片
        /// </summary>
        /// <param name="input"></param>
        /// <returns></returns>
        public async Task<FileSliceInfoGetOutputDto> UploadFileBySlice(FileInfoSliceUpdloadInput input)
        {
            var result = new FileSliceInfoGetOutputDto() { Result = true };
            if (input == null || string.IsNullOrWhiteSpace(input.Path))
            {
                throw AppResultException.Status400BadRequest("参数异常");
            }
            if (!input.BlobIndex.HasValue)
            {
                var fileInfo = await BusinessUploadAsync(
                  new List<IFormFile>() { input.File }, FileSource.Voide, string.Empty);
            }
            else
            {
                var uploadResult = await SaveBlob(input, async (info, success) =>
                {
                    if (success)
                    {
                        result = await UploadBySlice(info);
                    }
                });
                result.Result = uploadResult;
            }

            return result;
        }
        /// <summary>
        /// 保存切片文件
        /// </summary>
        /// <param name="input"></param>
        /// <param name="finished"></param>
        /// <returns></returns>
        private async Task<bool> SaveBlob(FileInfoSliceUpdloadInput input, Func<FileResumableInputModel, bool, Task> finished = null)
        {
            if (string.IsNullOrWhiteSpace(input.Key))
            {
                throw AppResultException.Status400BadRequest("参数异常");
            }
            var cacheKey = _cacheService.GetKey(CacheKeyConst.FileSlice, input.Key);
            var fileInfo = await _cacheService.GetCacheItemAsync<FileResumableInputModel>(cacheKey);
            if (fileInfo==null)
            {
                throw AppResultException.Status400BadRequest("invalid key");
            }
            var localDir = GetLocalTempPath();
            var tempDir = Path.Combine(localDir, $"_temp{input.Key}");
            var tempFile = Path.Combine(tempDir, input.BlobIndex.ToString());
            if (!Directory.Exists(tempDir))
            {
                Directory.CreateDirectory(tempDir);
            }
            var stream = input.File.OpenReadStream();
            using (var fs = new FileStream(tempFile, FileMode.Create, FileAccess.Write))
            {
                int len = 10485760;
                byte[] buffer = new byte[len];
                int readed;
                while ((readed = await stream.ReadAsync(buffer, 0, len)) > 0)
                {
                    await fs.WriteAsync(buffer, 0, Math.Min(readed, len));
                    await fs.FlushAsync();
                }
                fs.Close();
            }
            if (input.BlobIndex >= fileInfo.BlobCount - 1)
            {
                bool result = false;
                if (VerifyFiles(tempDir, fileInfo.BlobCount.Value))
                {
                    var storagePath = Path.Combine(localDir, fileInfo.StoreName);
                    result = await MergeFile(tempDir, storagePath, fileInfo.BlobCount.Value, fileInfo);
                }
                if (finished != null)
                {
                    await finished(fileInfo, result);
                }
                await _cacheService.RemoveCacheAsync(cacheKey);

                return result;
            }
            else
            {
                fileInfo.Index++;
                await _cacheService.SetCacheItemAsync(cacheKey, fileInfo, new Microsoft.Extensions.Caching.Distributed.DistributedCacheEntryOptions
                {
                    AbsoluteExpirationRelativeToNow = TimeSpan.FromDays(1)
                });
                return true;
            }
        }

        private bool VerifyFiles(string tempDir, long blobCount)
        {
            for (long i = 0; i < blobCount; i++)
            {
                string path = Path.Combine(tempDir, i.ToString());
                if (!File.Exists(path))
                {
                    return false;
                }
            }
            return true;
        }

        private async Task<bool> MergeFile(string tempDir, string storagePath, long blobCount, FileResumableInputModel fileInfo)
        {
            var result = false;
            var pathDir = Path.GetDirectoryName(storagePath);
            if (!Directory.Exists(pathDir))
            {
                Directory.CreateDirectory(pathDir);
            }
            try
            {
                using (var fs = new FileStream(storagePath, FileMode.Create, FileAccess.Write))
                {
                    for (long i = 0; i < blobCount; i++)
                    {
                        var blob = await File.ReadAllBytesAsync(Path.Combine(tempDir, i.ToString()));
                        await fs.WriteAsync(blob, 0, blob.Length);
                    }
                    await fs.FlushAsync();
                    fs.Close();
                }
                result = true;
            }
            catch (Exception ex)
            {
                if (File.Exists(storagePath))
                {
                    File.Delete(storagePath);
                }
                _logger.LogError(ex, $"文件切片上传合并时发生异常：\r\n{JsonConvert.SerializeObject(fileInfo)}");
            }
            finally
            {
                Directory.Delete(tempDir, true);
            }
            return result;
        }

        private string GetLocalTempPath()
        {
            return Path.Combine(Directory.GetCurrentDirectory(), "TempFiles");
        }

        private async Task<FileSliceInfoGetOutputDto> UploadBySlice(FileResumableInputModel fileInfo)
        {
            var result = new FileSliceInfoGetOutputDto()
            {
                FileName = fileInfo.FileName,
                DisplayName = fileInfo.FileName,
                FilePath = fileInfo.Path,
            };
            var storagePath = Path.Combine(GetLocalTempPath(), fileInfo.StoreName);
            var FileBytes = File.ReadAllBytes(storagePath);
            using (var ms = new MemoryStream(FileBytes))
            {
                IFormFile fromFile = new FormFile(ms, 0, ms.Length,
                    Path.GetFileNameWithoutExtension(storagePath),
                    Path.GetFileName(storagePath));
                var fileIds = await BusinessUploadAsync(
                    new List<IFormFile>() { fromFile }, FileSource.Voide, string.Empty);
                if (fileIds.Any())
                {
                    result.Id = fileIds.FirstOrDefault();
                    result.Result = true;
                }
            }
            return result;
        }
        public async Task<bool> CancelUpload(string key)
        {
            var result = true;
            var cacheKey = _cacheService.GetKey(CacheKeyConst.FileSlice, key);
            var fileInfo = await _cacheService.GetCacheItemAsync<FileResumableInputModel>(cacheKey);
            if (fileInfo != null)
            {
                try
                {
                    await _cacheService.RemoveCacheAsync(cacheKey);
                    var tempDir = Path.Combine(GetLocalTempPath(), fileInfo.StoreName);
                    if (Directory.Exists(tempDir))
                    {
                        Directory.Delete(tempDir, true);
                    }
                }
                catch { }
            }

            return result;
        }
        #endregion

        public async Task<Guid> UploadFileAsync(FileInfoInputModel1 model)
        {
            if (model == null || model.File == null)
            {
                throw AppResultException.Status400BadRequest("参数错误，未选择文件");
            }

            var fileIds = await BusinessUploadAsync(new List<IFormFile> { model.File }, FileSource.Article, string.Empty);
            return fileIds.FirstOrDefault();
        }

        public async Task<List<FileInfoModel>> GetFilesByIdListAsync(List<Guid> fileIds)
        {
            if (fileIds == null || !fileIds.Any())
            {
               return new List<FileInfoModel>();
            }

            var query = _context.Set<SysFileInfo>().Where(c => fileIds.Contains(c.Id));
            var result = await query.Select(s => new FileInfoModel
            {
                FileName = s.FileName,
                FolderId = s.FolderId,
                FilePath = s.FilePath,
                FileSize = s.FileSize,
                Filesuffix = s.Filesuffix,
                CreatedTime = s.CreatedTime,
                Id = s.Id,
                IsFolder = s.IsFolder,
                Remark = s.Remark
            }).ToListAsync();

            return result;
        }
    }
}
